package org.paboo.chart.qrcode;

import com.d_project.qrcode.ErrorCorrectLevel;
import com.d_project.qrcode.QRCode;

import org.paboo.util.LoggerFactoryUtils;
import org.paboo.util.ParameterObject;
import org.paboo.http.BaseServlet;
import org.paboo.http.HttpMethod;
import org.paboo.util.ZxingImage;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

public class QrdServlet extends BaseServlet {

    private static final long serialVersionUID = 6490130504990685386L;
    private static LoggerFactoryUtils logger = LoggerFactoryUtils.getInstance().load(QrdServlet.class);

    private static QRCode getQRCode(String text, int typeNumber, int errorCorrectLevel) {

        if (typeNumber == 0) {

            return QRCode.getMinimumQRCode(text, errorCorrectLevel);

        } else {

            QRCode qr = new QRCode();
            qr.setTypeNumber(typeNumber);
            qr.setErrorCorrectLevel(errorCorrectLevel);
            qr.addData(text);
            qr.make();

            return qr;

        }
    }

    private static int parseErrorCorrectLevel(String ecl) {
        if ("L".equals(ecl) || "l".equals(ecl)) {
            return ErrorCorrectLevel.L;
        } else if ("Q".equals(ecl) || "q".equals(ecl)) {
            return ErrorCorrectLevel.Q;
        } else if ("M".equals(ecl) || "m".equals(ecl)) {
            return ErrorCorrectLevel.M;
        } else if ("H".equals(ecl) || "h".equals(ecl)) {
            return ErrorCorrectLevel.H;
        } else {
            logger.error("invalid error correct level : " + ecl);
            throw new IllegalArgumentException("invalid error correct level : " + ecl);
        }
    }

    /**
     * GIFイメージを取得する。
     *
     * @param cellSize セルのサイズ(pixel)
     * @param margin   余白(pixel)
     */
    private static GIFImage createGIFImage(QRCode qrcode, int cellSize, int margin) throws IOException {

        int imageSize = qrcode.getModuleCount() * cellSize + margin * 2;

        GIFImage image = new GIFImage(imageSize, imageSize);

        for (int y = 0; y < imageSize; y++) {

            for (int x = 0; x < imageSize; x++) {

                if (margin <= x && x < imageSize - margin
                        && margin <= y && y < imageSize - margin) {

                    int col = (x - margin) / cellSize;
                    int row = (y - margin) / cellSize;

                    if (qrcode.isDark(row, col)) {
                        image.setPixel(x, y, 0);
                    } else {
                        image.setPixel(x, y, 1);
                    }

                } else {
                    image.setPixel(x, y, 1);
                }
            }
        }

        return image;
    }

    public void processRequest(HttpServletRequest request,
                               HttpServletResponse response, HttpMethod method) throws ServletException, IOException {
        if (method == HttpMethod.GET || method == HttpMethod.POST) {
            String data = getParameterWithDefault(request, "data", ParameterObject.DEFAULT_CONTECT);

            if (super.defaultCharacterEncoding != null) {
                data = new String(data.getBytes("ISO-8859-1"), super.defaultCharacterEncoding);
            }

            String output = getParameterWithDefault(request, "format", "png");

            int typeNumber = getIntParameterWithDefault(request, "type", 0);
            if (typeNumber < 0 || 10 < typeNumber) {
                sendErrorMsg(response, "illegal type number : " + typeNumber);
                return;
            }

            int margin = getIntParameterWithDefault(request, "margin", 2);
            if (margin < 0 || 32 < margin) {
                sendErrorMsg(response, "illegal margin : " + margin);
                return;
            }

            int cellSize = getIntParameterWithDefault(request, "size", 1);
            if (cellSize < 1 || 4 < cellSize) {
                sendErrorMsg(response, "illegal cell size : " + cellSize);
                return;
            }

            int errorCorrectLevel = parseErrorCorrectLevel(getParameterWithDefault(request, "level", "L"));

            QRCode qrcode = getQRCode(data, typeNumber, errorCorrectLevel);
            OutputStream out = new BufferedOutputStream(response.getOutputStream());

            if ("text".equalsIgnoreCase(output)) {

                response.setContentType("text/plain");
                try {
                    for (int row = 0; row < qrcode.getModuleCount(); row++) {
                        for (int col = 0; col < qrcode.getModuleCount(); col++) {
                            out.write((qrcode.isDark(row, col) ? "1" : "0").getBytes("ISO-8859-1"));
                        }
                        out.flush();
                        out.write("\r\n".getBytes("ISO-8859-1"));
                    }
                } catch (IOException e) {
                    logger.error(e);
                }
            } else if ("svg".equalsIgnoreCase(output)) {
                int imageSize = qrcode.getModuleCount() * cellSize + margin * 2;
                response.setContentType("image/svg+xml");
                ZxingImage.toSVGDocument(
                        ParameterObject.convertToZxingByteMatrix(qrcode),
                        new OutputStreamWriter(new BufferedOutputStream(response.getOutputStream()), StandardCharsets.ISO_8859_1.name()),
                        imageSize, imageSize, margin,
                        ZxingImage.BLACK, ZxingImage.WHITE);
            } else if ("jpeg".equalsIgnoreCase(output) || "png".equalsIgnoreCase(output)) {
                BufferedImage image = qrcode.createImage(cellSize, margin);

                response.setContentType("image/" + output.toLowerCase());

                try {
                    ImageIO.write(image, output.toLowerCase(), out);
                } catch (IOException e) {
                    logger.error(e);
                }

            } else if ("gif".equalsIgnoreCase(output)) {

                GIFImage image = createGIFImage(qrcode, cellSize, margin);

                response.setContentType("image/gif");

                try {
                    image.write(out);
                } catch (IOException e) {
                    logger.error(e);
                }

            } else {
                sendErrorMsg(response, "illegal output type : " + typeNumber);
            }
            out.close();
        } else {
            logger.error("HTTP " + method.name() + " IS NOT support.");
            response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Request error!!!");
        }
    }
}
